/*
 * @Author      : ZhouQiJun
 * @Date        : 2025-08-10 17:08:31
 * @LastEditors : ZhouQiJun
 * @LastEditTime: 2025-08-10 17:59:31
 * @Description : 绑定 map 实例上的事件
 */
import { Event } from '@ol/define'
import type { ProjCode } from '@ol/types'
import { Transforms } from '@ol/utils'
import { isNil, isNull, isPlainObject } from 'es-toolkit'
import type { MapBrowserEvent } from 'ol'

import type { FeatureLike } from 'ol/Feature'
import Map from 'ol/Map'
import { unByKey } from 'ol/Observable'
import type { Coordinate } from 'ol/coordinate'
import type { EventsKey } from 'ol/events'
import type BaseEvent from 'ol/events/Event'
import { type Extent } from 'ol/extent'
import { SimpleGeometry } from 'ol/geom'
import type { Pixel } from 'ol/pixel'

const EPSG3857 = 'EPSG:3857'
const EPSG4326 = 'EPSG:4326'

export type FeatureT = {
  feature: FeatureLike
  data?: unknown
  type?: string | undefined
  id?: string | number
}

export type Attachment = {
  features: FeatureT[]
  zoom?: number
  screen: Pixel
  coord: Coordinate
  coord3857: Coordinate
  extent: Extent
  extent3857: Coordinate
  center: Coordinate
  center3857: Coordinate
}

// eslint-disable-next-line no-unused-vars
export type OnEvent = (event: Event, attachment: Attachment, olEvent: BaseEvent) => void

/**
 * 绑定 OpenLayers `Map` 实例上的一个或多个事件，并在事件触发时提供经过封装的上下文信息。
 *
 * 该方法会监听地图事件（如 `'click'`, `'pointermove'`, `'singleclick'` 等），
 * 在回调中不仅提供原始事件对象，还会提供转换后的坐标、地图视图信息、范围、要素数据等。
 *
 * @param types - 要监听的事件类型，可以是单个事件（`MapEvent`/`RenderEvent`）或事件数组。
 * @param onEvent - 事件回调函数，接收三个参数：
 *   - `event` - 事件名称（字符串）
 *   - `attachment` - 附加数据对象（`Attachment` 类型），包含地图状态、坐标信息、要素等
 *   - `origin` - 原始事件对象（`BaseEvent` 类型）
 * @param map - `OpenLayers` 的地图实例
 *
 * @returns
 * - 如果 `map` 为 `null`，返回 `null`
 * - 否则返回一个函数 `clearMapEvents`，调用它可以移除绑定的事件监听
 *
 * @example
 * ```ts
 * const unbind = bindMapEvents('click', (eventName, attachment, olEvent) => {
 *   console.log('事件类型:', eventName)
 *   console.log('屏幕像素坐标:', attachment.screen)
 *   console.log('经纬度坐标:', attachment.coord)
 *   console.log('包含要素:', attachment.features)
 * }, mapInstance)
 *
 * // 解绑事件
 * unbind()
 * ```
 *
 * @remarks
 * - 回调的 `attachment.features` 数组中，每个元素都包含 `feature` 实例以及提取的 `data`, `type`, `id`
 * - 所有坐标信息（`coord`, `center`, `extent` 等）同时提供了 EPSG:4326 与 EPSG:3857 两种坐标系的值
 * - 对于点击位置无要素的情况，`features` 数组为空
 *
 * @see Attachment
 * @see FeatureT
 */
export default function bindMapEvents(types: Event, onEvent: OnEvent, map: Map) {
  if (isNull(map)) return null
  const _events = Array.isArray(types) ? types : [types]
  const view = map.getView()
  const projection = view.getProjection()
  // 从视图对象中获取投影编码
  const projCode = projection.getCode() as ProjCode
  const eventKeys: { [key: string]: EventsKey }[] = []
  _events.forEach((event) => {
    //@ts-ignore
    const key = map.on(event, proxyOnEvent)
    eventKeys.push({ [event]: key })
  })

  return unbindMapEvents

  function proxyOnEvent(e: MapBrowserEvent<UIEvent>) {
    // console.log(e)
    let hit = false
    if (e.pixel) {
      hit = map.hasFeatureAtPixel(e.pixel, {
        //layerFilter: (layer) => ['VectorLayer'].includes(layer.constructor.name),
        hitTolerance: 2,
      })
    }
    const screen = e.pixel
    const zoom = view.getZoom()
    const _center = view.getCenter() as Coordinate
    const center3857 = Transforms.point(_center, projCode, EPSG3857)
    const center = Transforms.point(_center, projCode, EPSG4326)

    const _extent = view.calculateExtent()
    const extent3857 = Transforms.extent(_extent, projCode, EPSG3857)
    const extent = Transforms.extent(_extent, projCode, EPSG4326)

    const coord3857 = Transforms.point(e.coordinate, projCode, EPSG3857) // map.getEventCoordinate(e) 拿不到坐标
    const coord = Transforms.point(e.coordinate, projCode, EPSG4326)
    let _features: FeatureT[] = []
    const attachment = {
      features: _features,
      screen,
      zoom,
      coord,
      coord3857,
      extent,
      extent3857,
      center,
      center3857,
    }
    // 点击位置没有要素
    if (!hit) {
      onEvent(e.type as unknown as Event, attachment, e)
      return
    }

    const features = map.getFeaturesAtPixel(e.pixel)
    _features = features.map((feat) => {
      // feat.getProperties() 没有附加数据，会是 feature 不把它作后备数据
      const type = feat.getGeometry()?.getType()
      let id = feat.getId()
      const data = feat.get('data') || feat.getProperties()
      if (isPlainObject(data) && data.geometry instanceof SimpleGeometry) {
        delete data.geometry
      }
      if (isNil(id) && !isNil(data?.id)) id = data.id
      return {
        feature: feat,
        data,
        type,
        id,
      }
    })

    attachment.features = _features
    onEvent(e.type as unknown as Event, attachment, e)
  }

  function unbindMapEvents(events = _events) {
    events.forEach((event) => {
      eventKeys.forEach((obj) => {
        unByKey(obj[event])
      })
    })
    console.log('取消事件监听', events)
  }
}
